package com.reactor.错误处理;

import java.util.Random;
import java.util.concurrent.atomic.LongAdder;

import org.junit.Test;
import org.reactivestreams.Publisher;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;

/**在讨论错误处理操作符的时候，我们借助命令式编程风格的 try 代码块来作比较。我们都很熟悉在 try-catch 代码块中处理异常的几种方法。常见的包括如下几种：

捕获并返回一个静态的缺省值。
捕获并执行一个异常处理方法或动态计算一个候补值来顶替。
捕获，并再包装为某一个 业务相关的异常，然后再抛出业务异常。
捕获，记录错误日志，然后继续抛出。
使用 finally 来清理资源，或使用 Java 7 引入的 "try-with-resource"。
 * @author DELL
 *
 */
public class Demo {

	@Test
	public void testErrorHandling() {
	    Flux.range(1, 6)
	    		// 当i为3时会导致异常。
	            .map(i -> 10/(i-3)) // subscribe方法的第二个参数定义了对错误信号的处理，从而测试方法exit为0（即正常退出），可见错误没有蔓延出去。不过这还不够~
	            .map(i -> i*i)
	            .subscribe(System.out::println, System.err::println);
	}

	@Test
	public void testErrorHandling2() {
		Flux.range(1, 6)
	    .map(i -> 10/(i-3))
	    .onErrorReturn(0)   // 当发生异常时提供一个缺省值0
	    .map(i -> i*i)
	    .subscribe(System.out::println, System.err::println);
	}

	@Test
	public void testErrorHandling3() {
		Flux.range(1, 6)
	    .map(i -> 10/(i-3))
	    .onErrorResume(e -> Mono.just(new Random().nextInt(6))) // 提供新的数据流
	    .map(i -> i*i)
	    .subscribe(System.out::println, System.err::println);
	}

	@Test
	public void testErrorHandling4() {
		Flux.just("timeout1", "timeout2")
	    .flatMap(k -> callExternalService(k))   // 调用外部服务；
	    .onErrorResume(e -> getFromCache(e));   // 如果外部服务异常，则从缓存中取值代替。
	}

	private Publisher<String> callExternalService(String k) {
		return null;
	}

	private Publisher getFromCache(Throwable e) {
		return null;
	}

	@Test
	public void test4(){
		Flux.just("timeout1")
	    .flatMap(k -> callExternalService(k))   // 调用外部服务
	    .onErrorMap(original -> new BusinessException("SLA exceeded", original)); // 如果外部服务异常，将其包装为业务相关的异常后再次抛出
	}

	@Test
	// test4这一功能其实也可以用onErrorResume实现，略麻烦一点
	public void test5(){
		Flux.just("timeout1")
	    .flatMap(k -> callExternalService(k))
	    .onErrorResume(original -> Flux.error(
	        new BusinessException("SLA exceeded", original)
	    ));
	}

	@Test
	public void test6(){
		Flux.just("timeout1", "timeout1")
	    .flatMap(k -> callExternalService(k))
	    .doOnError(e -> {   // 只读地拿到错误信息，错误信号会继续向下游传递
	        log("uh oh, falling back, service failed for key " + e);    // 记录日志
	    })
	    .onErrorResume(e -> getFromCache(e));
	}

	private void log(String string) {
		// TODO Auto-generated method stub

	}

	@Test
	//使用 finally 来清理资源，或使用 Java 7 引入的 "try-with-resource"
	public void test7(){
		/*Flux.using(
		        () -> getResource(),    // 第一个参数获取资源
		        resource -> Flux.just(resource.getAll()),   // 第二个参数利用资源生成数据流
		        MyResource::clean   // 第三个参数最终清理资源
		);*/
	}

	@Test
	// doFinally在序列终止（无论是 onComplete、onError还是取消）的时候被执行， 并且能够判断是什么类型的终止事件（完成、错误还是取消），以便进行针对性的清理。如：
	public void test8(){
		LongAdder statsCancel = new LongAdder();    // 用LongAdder进行统计

		Flux<String> flux =
		Flux.just("foo", "bar")
		    .doFinally(type -> {
		        if (type == SignalType.CANCEL)  // doFinally用SignalType检查了终止信号的类型
		          statsCancel.increment();  // 如果是取消，那么统计数据自增
		    })
		    .take(1);   // take(1)能够在发出1个元素后取消流
	}

}
